#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>

typedef struct {
  ngx_flag_t is_rewrite;
}ngx_http_block_ctx_t;



typedef struct{
    ngx_array_t *exceptions;
    ngx_str_t redirect_url;
    ngx_flag_t redirect;
    ngx_flag_t block;
}ngx_http_block_loc_conf_t;


static void *ngx_http_block_create_loc_conf(ngx_conf_t *cf);
static char *ngx_http_block_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child);
//static char *ngx_http_block_exceptions(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);

//static char *ngx_http_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static ngx_int_t ngx_http_block(ngx_conf_t *cf);
u_char* parse_url(ngx_pool_t *pool, const ngx_str_t* url);

static ngx_command_t ngx_http_block_commands[] = {
   			
   	{ ngx_string("block"),
      NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
      ngx_conf_set_flag_slot,
      NGX_HTTP_LOC_CONF_OFFSET,
      offsetof(ngx_http_block_loc_conf_t, block),
      NULL },

    { ngx_string("block_exception"),
      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
      ngx_conf_set_str_array_slot,
      NGX_HTTP_LOC_CONF_OFFSET,
      offsetof(ngx_http_block_loc_conf_t, exceptions),
      NULL },

    { ngx_string("block_redirect"),
      NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
      ngx_conf_set_flag_slot,
      NGX_HTTP_LOC_CONF_OFFSET,
      offsetof(ngx_http_block_loc_conf_t, redirect),
      NULL },
    
    { ngx_string("block_redirect_url"),
      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
      ngx_conf_set_str_slot,
      NGX_HTTP_LOC_CONF_OFFSET,
      offsetof(ngx_http_block_loc_conf_t, redirect_url),
      NULL },

      ngx_null_command

};


static ngx_http_module_t  ngx_http_block_module_ctx = {
    NULL,                          /* preconfiguration */
    ngx_http_block,                /* postconfiguration */

    NULL,                          /* create main configuration */
    NULL,                          /* init main configuration */

    NULL,                          /* create server configuration */
    NULL,                          /* merge server configuration */

    ngx_http_block_create_loc_conf,/* create location configuration */
    ngx_http_block_merge_loc_conf  /* merge location configuration */
};


ngx_module_t  ngx_http_block_module = {
    NGX_MODULE_V1,
    &ngx_http_block_module_ctx,    /* module context */
    ngx_http_block_commands,       /* module directives */
    NGX_HTTP_MODULE,               /* module type */
    NULL,                          /* init master */
    NULL,                          /* init module */
    NULL,                          /* init process */
    NULL,                          /* init thread */
    NULL,                          /* exit thread */
    NULL,                          /* exit process */
    NULL,                          /* exit master */
    NGX_MODULE_V1_PADDING
};



static ngx_int_t ngx_http_block_handler(ngx_http_request_t *r){

	/*
	 * 1、获取refer http header
     * 2、判断是否在例外列表内
     * 3、是则跳出该模块，正常处理
     * 4、否则禁止访问，跳到5
     * 5、是否设置了重定向
     * 6、是则跳到重定向url，如果没有设置url，则返回错误
     * 7、否则显示错误页面
     */

     

     ngx_int_t found;
     ngx_array_t * exceptions;
     ngx_http_block_loc_conf_t* blcf;
     //ngx_log_t  *log;
     ngx_uint_t i;


     ngx_http_block_ctx_t* ctx=ngx_http_get_module_ctx(r, ngx_http_block_module);
     if (ctx == NULL)
     {
       ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_block_ctx_t));
       if (ctx == NULL)
       {
         return NGX_ERROR;
       }
       ctx->is_rewrite=0;
       ngx_http_set_ctx(r,ctx,ngx_http_block_module);
     }

     if (ctx->is_rewrite==1)
     {
       r->uri_changed=0;
       return NGX_DECLINED;
     }

     blcf= ngx_http_get_module_loc_conf(r, ngx_http_block_module);

     if (blcf->block==NGX_CONF_UNSET || blcf->block==0)
     {
        return NGX_DECLINED;
     }

     ngx_table_elt_t *referer=r->headers_in.referer;

     if (!referer || referer->value.len==0)
     {
       return NGX_DECLINED;
     }



     if (blcf->exceptions==NGX_CONF_UNSET_PTR)
     {
        return NGX_HTTP_NOT_ALLOWED;
     }

     exceptions=blcf->exceptions;

     u_char* host=parse_url(r->pool,&(referer->value));

     if (host == NULL)
     {

       ngx_log_debug(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "host url error");
       return NGX_HTTP_INTERNAL_SERVER_ERROR;
     }

     ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "my parse host: \"%s\"", host);

     found=0;

     for (i = 0; i < exceptions->nelts; ++i){

        ngx_str_t * one=(ngx_str_t*)((char*)(exceptions->elts)+exceptions->size*i);

        if(ngx_strstr(one->data, host)){

            found=1;
            break;
        }
        /*
        if (referer->value.len!=one->len){
            continue;
        }

        if (ngx_strncmp(referer->value.data, one->data, referer->value.len)==0){
          found=1;
          break;
        }
        */

     }

     if (!found){

        if (blcf->redirect!=NGX_CONF_UNSET)
        {
          ctx->is_rewrite=1;
          r->uri_changed=1;
          r->uri=blcf->redirect_url;
          return NGX_DECLINED;
        }
        return NGX_HTTP_NOT_ALLOWED;
     }


    //ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0, "http block referer: \"%V\", on the exceptions set", &referer->value);
     return NGX_DECLINED;
     

     //return NGX_DECLINED;

}


u_char* parse_url(ngx_pool_t *pool, const ngx_str_t* url){

  ngx_str_t* host=ngx_pcalloc(pool,sizeof(ngx_str_t));
  if (host ==  NULL)
  {
    return NULL;
  }

  u_char* scheme_tail=(u_char*)ngx_strstr(url->data, "//");
  if (scheme_tail == NULL)
  {
     return NULL;
  }

  scheme_tail+=2;
  host->data=scheme_tail;

  u_char* host_tail=(u_char*)ngx_strstr(host->data, "/");
    if (host_tail == NULL)
  {
      host->len=url->len-(scheme_tail-url->data) ;
  }else{
      ngx_int_t len=0;
      while(scheme_tail!=host_tail){

          len++;
          scheme_tail++;
      }
      host->len=len;
  }

  host->len=host->len+1;

  //ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pool->log, 0, "parse_url host: \"%V\"", &(host->data));

  u_char* dst=ngx_pstrdup(pool, host);
  *(dst+ host->len-1)='\0';
  
  return  dst;

}

static void *
ngx_http_block_create_loc_conf(ngx_conf_t *cf)
{
    ngx_http_block_loc_conf_t  *conf;

    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_block_loc_conf_t));
    if (conf == NULL) {
        return NULL;
    }

    conf->redirect = NGX_CONF_UNSET;
    conf->block = NGX_CONF_UNSET;
    ngx_str_null(&(conf->redirect_url));
    conf->exceptions= NGX_CONF_UNSET_PTR;

    return conf;
}


static char * ngx_http_block_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child){

    ngx_http_block_loc_conf_t  *prev = parent;
    ngx_http_block_loc_conf_t  *conf = child;

    ngx_uint_t i;

    //合并redirect_url
    ngx_conf_merge_str_value(conf->redirect_url, prev->redirect_url, " ");

    //合并exceptions

    ngx_array_t* parentException=(ngx_array_t*)prev->exceptions;
    ngx_array_t* childException=(ngx_array_t*)conf->exceptions;

    if (parentException!=NGX_CONF_UNSET_PTR){

      ngx_str_t** parentEecepStr=parentException->elts;

    	if (childException!=NGX_CONF_UNSET_PTR){
    		ngx_str_t** push=ngx_array_push_n(childException, parentException->nelts);

    		for (i = 0; i < parentException->nelts; ++i){

    			ngx_str_t *elem= parentEecepStr[i] ;//(ngx_str_t*)((char*)(parentException->elts)+parentException->size*i);
          push[i]=elem;
    		}

    	}else{
    		conf->exceptions=prev->exceptions;
    	}
    }
    

    return NGX_CONF_OK;
}

/*
static char* ngx_http_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ngx_http_core_loc_conf_t  *clcf;

    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
    clcf->handler = ngx_http_block_handler;

    return NGX_CONF_OK;
}
*/


static ngx_int_t ngx_http_block(ngx_conf_t *cf){
    ngx_http_handler_pt        *h;
    ngx_http_core_main_conf_t  *cmcf;

    cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);

    h = ngx_array_push(&cmcf->phases[NGX_HTTP_REWRITE_PHASE].handlers);
    if (h == NULL) {
        return NGX_ERROR;
    }

    *h = ngx_http_block_handler;

    return NGX_OK;
}
